(ns fxbattle.server.playercomms
 (:import
   (java.net Socket ServerSocket)
   (fxbattle.protos ClientProtos ClientProtos$RequestToPlay ClientProtos$SetPath))
 (:require fxbattle.server.core fxbattle.server.protobuilder)
 (:gen-class))

;;; Blocking wait for a client to connect. Open a new socket for communication
;;; with that client and return the new socket.
(defn waitForClient
  "Waits for a client to connect"
  [#^ServerSocket socket]
  (println "Server Waiting for Connection.")
  (.accept socket))

;;; Block for the client to make a game request with a player name. Send the
;;; player data to the client.
(defn waitForRequestToPlay
  "Waits for a request to play and returns the map"
  [socket game]
  (println "Server waiting for a request to play.")
  (let [request (. ClientProtos$RequestToPlay parseDelimitedFrom (.getInputStream socket))
        player (fxbattle.server.core/add-player (.getName request) game)]
    (println "Added Player" (:name player))
    (.writeDelimitedTo (fxbattle.server.protobuilder/make-RespondToPlayRequest player)
      (.getOutputStream socket))
    player))

;;;
(defn player-listener
  "Listens to the given port for ClientProtos$SetPath messages"
  [player socket game]
  (println "Waiting for Set Path")
  (while (.isConnected socket)
    (let [message (. ClientProtos$SetPath parseDelimitedFrom (.getInputStream socket))
          mpath   (.getToChange message)
          mactive (.getIsActive message)
          path    (fxbattle.server.protobuilder/set-path mpath mactive player game)]
      (if path
        (.writeDelimitedTo (fxbattle.server.protobuilder/make-FlowStateUpdate path player)
          (.getOutputStream socket))))))

;;;
(defn sendInitialUpdate
  "Send the game state to a client that just connected"
  [socket game]
  (.writeDelimitedTo (fxbattle.server.protobuilder/make-InitialStateUpdate game)
    (.getOutputStream socket)))

;;;
(defn sendUpdate
  "Send the game state to a client that just connected"
  [player socket game]
  (.writeDelimitedTo (fxbattle.server.protobuilder/make-StateUpdate player game)
    (.getOutputStream socket)))

;;;
(defn player-server
  "Sends update to the player on the given socket"
  [player socket game]
  (while (not @(:started game))
    (. Thread (sleep 100)))
  (.start (Thread. (fn [] (player-listener player socket game))))
  (try
    (sendInitialUpdate socket game)
    (while (.isConnected socket)
      (sendUpdate player socket game)
      (. Thread (sleep 250)))
    (finally
      (println "Player" (:name player) "disconnected")
      (fxbattle.server.core/remove-player player game)
      (println "Close Socket")
      (.close socket))))

;;;
(defn listen-for-players
  "Listens for player connections, starting new threads to communicate with
each player and "
  [port number-of-players game]
  (println "Waiting for" number-of-players "players")
  (let [serversocket (new ServerSocket port)]
    (loop [player-number 1]
      (let [socket (waitForClient serversocket)
            player (waitForRequestToPlay socket game)]
        (println "Player" player-number "connected")
        (.start (Thread. (fn [] (player-server player socket game))))
        (if-not (= player-number number-of-players)
          (recur (inc player-number)))))))
